using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Devices.Sensors;

namespace nBodyPhone
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        Viewport viewport;
        SpriteBatch spriteBatch;

        // FPS Display Variables
        SpriteFont font;
        Vector2 fpsPosition;
        string fpsString;
        Vector2 fpsStringOrigin;

        Texture2D point;
        Texture2D circle;
        int numBodies = 16;
        int spacing = 16;

        int numAssimilations = 0;
        float sunMass = (float)1;
        float sonMass = (float).1;
        long iterations = 0;

        IList<Body> bodies = new List<Body>();
        Random rand = new Random();

        Timer timer;

        bool processBodies;
        bool dampField;

        Accelerometer accelSensor;
        AccelerometerReadingEventArgs accelArgs;
        bool accelActive;
        string accelString = "";
        Vector2 accelPosition;
        Vector2 accelStringOrigin;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Allows the update method and draw method to run free!
            IsFixedTimeStep = false;


            // Frame rate is 30 fps by default for Windows Phone.
            //TargetElapsedTime = TimeSpan.FromTicks(333333);

            // Extend battery life under lock.
            InactiveSleepTime = TimeSpan.FromSeconds(1);

            //Enable Touch panel input
            TouchPanel.EnabledGestures = GestureType.Tap;

        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            viewport = graphics.GraphicsDevice.Viewport;
            //accelSensor = new Accelerometer();
            //// add the accelerometer event hander to the accelerometer sensor.
            //accelSensor.ReadingChanged += new EventHandler<AccelerometerReadingEventArgs>(AccelerometerReadingChanged);
            //// Start the accelerometer
            //try
            //{
            //    accelSensor.Start();
            //    accelActive = true;
            //}
            //catch (AccelerometerFailedException e)
            //{
            //    accelActive = false;
            //}

            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            font = Content.Load<SpriteFont>("Segoe32");
            fpsPosition = new Vector2(viewport.Width / 2, viewport.Height / 8);
            accelPosition = new Vector2(viewport.Width / 2, viewport.Height * 7 / 8);

            circle = Content.Load<Texture2D>("circle16");
            point = Content.Load<Texture2D>("point");

            bodies = GenerateBodies(numBodies, spacing);

            timer = new Timer();

            processBodies = false;
            dampField = true;                  // Damps velocities if outside a bound... not really a good thing

            System.Diagnostics.Debug.WriteLine("width: " + viewport.Width + " height: " + viewport.Height);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Generate the FPS Counter
            timer.update(gameTime);

            fpsString = "FPS: " + timer.getFps().ToString();
            fpsStringOrigin = font.MeasureString(fpsString) / 2;

            if (accelActive)
            {
                accelString = "aX: " + accelArgs.X + "/taY: " + accelArgs.Y + "/taZ: " + accelArgs.Z;
            }
            else
            {
                //accelString = "";
            }
            //accelStringOrigin = font.MeasureString(fpsString) / 2;

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // Deal this input

            while (TouchPanel.IsGestureAvailable)
            {
                GestureSample gs = TouchPanel.ReadGesture();
                switch (gs.GestureType)
                {
                    case GestureType.Tap:
                        processBodies = !processBodies;
                        break;
                    case GestureType.VerticalDrag:
                        // This doesn't work, so VerticalDrag is disabled in constructor.
                        processBodies = !processBodies;
                        GenerateBodies(numBodies);
                        break;
                }
            }

            // Update each body
            if (processBodies)
            {
                iterations++;
                updateBodies();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            //System.Diagnostics.Debug.WriteLine(iterations);
            GraphicsDevice.Clear(Color.Black);

            // TODO: Add your drawing code here

            spriteBatch.Begin();

            spriteBatch.DrawString(font, fpsString, fpsPosition, Color.White, 0, fpsStringOrigin, 1.0f, SpriteEffects.None, 0.5f);

            spriteBatch.DrawString(font, accelString, accelPosition, Color.White, 0, accelStringOrigin, 1.0f, SpriteEffects.None, 0.5f);

            foreach (Body body in bodies)
            {
                //spriteBatch.Draw(body.texture, body.texturePosition, body.color);
                spriteBatch.Draw(body.texture, body.texturePosition, null, body.color, 0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0f);
                if (!processBodies)
                {
                    spriteBatch.DrawString(font, body.toString(), body.texturePosition, Color.Yellow, 0, Vector2.Zero/*font.MeasureString(body.toString())/2*/, 0.3f, SpriteEffects.None, 0f);
                }
            }

            foreach (Body body in bodies)
            {
                if (!processBodies)
                {
                    DrawLine(spriteBatch, point, body.position, bodies[0].position, Color.Pink);
                }

                DrawLine(spriteBatch, point, body.position, Vector2.Add(body.position, body.velocity * 50), Color.Green);
            }
            spriteBatch.End();

            base.Draw(gameTime);
        }

        IList<Body> GenerateBodies(int numberOfBodies)
        {
            IList<Body> bodies = new List<Body>();
            Random rand = new Random();
            Vector2 position = new Vector2((float)viewport.Width / 2,
                (float)viewport.Height / 2);
            Vector2 velocity = new Vector2((float)0, (float)0);

            bodies.Insert(0, new Body(Color.Yellow, circle, position, velocity, sunMass, sunMass));
            position.X = viewport.Width / 2 + 16;

            velocity.X = (float)0.4;
            velocity.Y = (float)0.8;
            bodies.Insert(1, new Body(Color.Red, circle, position, velocity, sonMass, sonMass));

            //position.X = 32;
            //position.Y = 32;
            //bodies.Insert(2, new Body(Color.AntiqueWhite, circle, position, Vector2.Zero, 10.0f, 1.0f));

            //position.X = 48;
            //position.Y = 32;
            //bodies.Insert(3, new Body(Color.AntiqueWhite, circle, position, Vector2.Zero, 1.0f, 1.0f));

            for (int i = 1; i < numberOfBodies; i++)
            {

                position.X = viewport.Width / 16 + (float)rand.Next(viewport.Width / 4);
                position.Y = viewport.Height / 16 + (float)rand.Next(viewport.Height / 4);
                velocity.X = (float)rand.NextDouble();
                velocity.Y = (float)rand.NextDouble();
                Color color = new Color(0, rand.Next(255), rand.Next(255));
                bodies.Insert(i, new Body(color, circle, position, velocity, (float)rand.NextDouble() / 250, (float)rand.NextDouble() / 250));
            }

            return bodies;
        }

        IList<Body> GenerateBodies(int numberOfBodies, int spacing)
        {
            IList<Body> bodies = new List<Body>();
            Random rand = new Random();
            Vector2 position = new Vector2((float)viewport.Width / 2,
                (float)viewport.Height / 2);
            Vector2 velocity = new Vector2((float)0, (float)0);

            bodies.Insert(0, new Body(Color.Yellow, circle, position, velocity, sunMass, sunMass));
            bodies[0].updatePosition(position);
            position.X = viewport.Width / 2 + 16;

            velocity.X = (float)0.4;
            velocity.Y = (float)0.8;
            //bodies.Insert(1, new Body(Color.Red, circle, position, velocity, sonMass, sonMass));

            //position.X = 32;
            //position.Y = 32;
            //bodies.Insert(2, new Body(Color.AntiqueWhite, circle, position, Vector2.Zero, 10.0f, 1.0f));

            //position.X = 48;
            //position.Y = 32;
            //bodies.Insert(3, new Body(Color.AntiqueWhite, circle, position, Vector2.Zero, 1.0f, 1.0f));
            int i = 0;
            for (int x = 1; x <= Math.Sqrt(numberOfBodies/2); x++)
            {
                for (int y = 1; y <= Math.Sqrt(numberOfBodies/2); y++)
                {
                    i++;
                    position.X = x * spacing;
                    position.Y = y * spacing;
                    velocity.X = (float)rand.NextDouble();
                    velocity.Y = (float)rand.NextDouble();
                    Color color = new Color(0, rand.Next(255), rand.Next(255));
                    bodies.Insert(i, new Body(color, circle, position, velocity, (float)rand.NextDouble() / 250, (float)rand.NextDouble() / 250));
                }
            }

            i = 0;
            for (int x = 1; x <= Math.Sqrt(numberOfBodies/2); x++)
            {
                for (int y = 1; y <= Math.Sqrt(numberOfBodies/2); y++)
                {
                    i++;
                    position.X = viewport.Width - x * spacing;
                    position.Y = viewport.Height - y * spacing;
                    velocity.X = (float)rand.NextDouble();
                    velocity.Y = (float)rand.NextDouble();
                    Color color = new Color(0, rand.Next(255), rand.Next(255));
                    bodies.Insert(i, new Body(color, circle, position, velocity, (float)rand.NextDouble() / 250, (float)rand.NextDouble() / 250));
                }
            }
            
            return bodies;
        }

        private void updateBodies()
        {
            for (int i = 1; i < bodies.Count(); i++)
            {
                Body body = bodies[i];
                for (int j = 0; j < bodies.Count(); j++)
                {
                    Body target = bodies[j];
                    body.move = new Vector2((float)0, (float)0);
                    //if (body.mass != target.mass && body.mass != sunMass /*&& (target.mass == sunMass || target.mass == sonMass)*/)
                    if (i != 0 && i != j)
                    {
                        body.distance = Vector2.Subtract(body.position, target.position);
                        double dist = Math.Sqrt(body.distance.X * body.distance.X + body.distance.Y * body.distance.Y);
                        if (body.distance.X != 0)
                        {
                            body.force.X = body.mass * target.mass / body.distance.X * body.distance.X;
                        }
                        else
                        {
                            body.force.X = (float)0.0;
                        }

                        if (body.distance.Y != 0)
                        {
                            body.force.Y = body.mass * target.mass / body.distance.Y * body.distance.Y;
                        }
                        else
                        {
                            body.force.Y = (float)0.0;
                        }

                        body.move.X += body.force.X * (Math.Abs(body.distance.X) / (float)dist);
                        body.move.Y += body.force.Y * (Math.Abs(body.distance.Y) / (float)dist);

                        if (body.position.X < target.position.X) { body.velocity.X += body.move.X; }
                        else { body.velocity.X -= body.move.X; }

                        if (body.position.Y < target.position.Y) { body.velocity.Y += body.move.Y; }
                        else { body.velocity.Y -= body.move.Y; }

                    }

                    body.updatePosition(Vector2.Add(body.position, body.velocity));

                    if (dampField)
                    {
                        if (body.position.X > viewport.Width - body.texture.Width || body.position.X < 0)
                        {
                            body.velocity.X = -body.velocity.X;
                        }

                        if (body.position.Y > viewport.Height - body.texture.Height || body.position.Y < 0)
                        {
                            body.velocity.Y = -body.velocity.Y;
                        }
                    }

                    // Check collision, add mass if so and delete if smaller
                    if (body.texturePosition.X >= target.texturePosition.X && body.texturePosition.X <= target.texturePosition.X + target.texture.Width
                        && body.texturePosition.Y >= target.texturePosition.Y && body.texturePosition.Y <= target.texturePosition.Y + target.texture.Height)
                    {
                        body.ResolveCollision(target);
                        
                        //body.alive = false;
                        //if (body.mass < target.mass)
                        //{
                        //    numAssimilations += 1;
                        //    target.mass += body.mass;
                        //    //target.velocity = Vector2.Add(target.velocity, body.velocity);
                        //    target.color = new Color(body.color.R + 10, 0f, 0f);
                        //    bodies.RemoveAt(i);
                        //    accelString = "Assimilations: " + numAssimilations;
                        //    //foreach (Body thing in bodies)
                        //    //{
                        //    //    System.Diagnostics.Debug.WriteLine(thing.mass.ToString());
                        //    //}
                        //    accelStringOrigin = font.MeasureString(accelString) / 2;
                        //}
                        //else if (body.mass > target.mass)
                        //{
                        //    numAssimilations += 1;
                        //    body.mass += target.mass;
                        //    //body.velocity = Vector2.Add(body.velocity, target.velocity);
                        //    body.color = new Color(body.color.R + 10, 0f, 0f);
                        //    bodies.RemoveAt(j);
                        //    accelString = "Assimilations: " + numAssimilations;
                        //    accelStringOrigin = font.MeasureString(accelString) / 2;
                        //}
                        //break;
                    }
                }
            }
        }

        public void RemoveDeadBodies()
        {
            for (int i = 0; i < bodies.Count(); i++)
            {
                if (bodies[i].alive == false)
                {
                    bodies.RemoveAt(i);
                }
            }
        }
        // Accelerometer Event Handler
        public void AccelerometerReadingChanged(object sender, AccelerometerReadingEventArgs e)
        {
            accelArgs = e;
        }

        void DrawLine(SpriteBatch sprBatch, Texture2D spr, Vector2 a, Vector2 b, Color col)
        {
            Vector2 Origin = new Vector2(0.5f, 0.0f);
            Vector2 diff = b - a;
            float angle;
            Vector2 Scale = new Vector2(1.0f, diff.Length() / spr.Height);

            angle = (float)(Math.Atan2(diff.Y, diff.X)) - MathHelper.PiOver2;

            sprBatch.Draw(spr, a, null, col, angle, Origin, Scale, SpriteEffects.None, 1.0f);
        }
    }
}
